package GBP;

import java.util.*;
import java.io.*;

/**
 * RunGBP.java
 * Created: 2006-01-05 23:30
**/

/**
 * This is the main program performing
 * the Graph-Bipartitioning-Problem experiment.
 *
 * @author T.S.Yo
**/

public class RunGBP{
    private ReadIni param;
    private GraphDef graph;
//---------------------------------------------------------------------
// Constructor
    /** Constructor **/
    public RunGBP(final String iFile){
        // Read in parameters
        //System.out.println("Start reading parameters....");
        param = new ReadIni(iFile);
        //System.out.println("Parameters OK");
        //System.out.println("");

        // Read in graph definition
        graph = new GraphDef(param.gFile,param.nNode);
        //System.out.println("Start reading graph definition....");
        graph.ReadGBP(param.gFile);
        //System.out.println(graph1);    // For testing
        //System.out.println("Graph definition OK");
        //System.out.println("");
        
    }
    
//---------------------------------------------------------------------
// Multi-Start KLFM
    /** Methods to run multi-start KLFM with fixed times **/
    public void runFixedMS(){        
        // Initialize statistics
        int maxTry = param.popSize + param.maxGen;
        int fmCount = 0;
        int max = -9999;
        int min = 9999;
        int optCount = 0;
        int[] fit = new int[maxTry];
        String[] genOut = new String[4];
        
        // Initialize population (with KLFM)
        Individual[] pop1 = new Individual[maxTry];
        for (int i = 0; i < maxTry; i++){
            pop1[i] = new Individual(param.nNode,graph);
            // Initialize with KLFM
            fmCount += iterKLFM(pop1[i]);
            
            // Record statistics
            fit[i] = pop1[i].getFitness();
            //System.out.print((i+1)+"  #FM: "+fmCount[i]+"    Fitness: "+fit+"\n");    // For testing
            if(fit[i] >= param.optFit){
                System.out.print("*");
                optCount++;            // optCount: the probability of optimum found
            } //else {
                System.out.print(".");
            //}
        }
        System.out.print("\n");
        
        // Output statistics
        genOut =(calStat(pop1));
        genOut[0] = "\nFixed-Size Multi-Start FM" +
                    "\nTotal tries: " + maxTry + "    Total FM: "+fmCount+
                    "    Total optimum: " + optCount + genOut[0];
        System.out.print(genOut[0]);
        max = Integer.parseInt(genOut[1]);
        min = Integer.parseInt(genOut[2]);
        outputStat(param.oFile,genOut[0]);
    }

    /** Methods to run multi-start KLFM until optimum found **/
    public void runContMS(){        
        //  Initialize statistics
        int maxTry = param.popSize + param.maxGen;
        int max = -9999;
        int min = 9999;
        int inFM = 1;
        int outFM = 0;
        int outCount = 0;
        String result = "\nContinuous Multi-Start FM:";
        
        // Initialize population (with KLFM)
        Individual ind1 = new Individual(param.nNode,graph);
        KLFM fm1 = new KLFM(graph,ind1);
        
        // Outer loop:
        while((ind1.getFitness() < param.optFit)&&(outCount<maxTry)){
            ind1 = new Individual(param.nNode,graph);    // Generate new random solution
            //System.out.print("#"+outCount);    //For testing
            //Inner loop:
            inFM = iterKLFM(ind1);
            //System.out.print(inFM);    //For testing
            //System.out.print("    Fitness : "+ind1.getFitness()+"\n");    //For testing
        // Record statistics
            int fit = ind1.getFitness();
            outCount++;                         // # of new solution
            outFM += inFM;                      // Sum up total FM counts
            if(fit > max){                      // max: maximum fitness
                max = fit;
            }
            if(fit < min){                      // min:  minimum fitness
                min = fit;
            }
        }
        // Output statistics
        result+=("\nTotal tries: "+outCount+"    Total FM: "+outFM);
        result+=("\nFitness  Max: "+max+"    Min: "+min);
        System.out.println(result);
        outputStat(param.oFile,result);
    }

//---------------------------------------------------------------------
// GA-wrapped KLFM
    /**  Methods to run EC with KLFM-initialization **/ 
    public void runGA(final boolean iniWithFM){
        //  Initialize statistics
        int max = -99999;                   // Max fitness of a generation
        int min = 99999;                    // Min fitness of a generation
        double mean = 0.0;                  // Mean fitness of a generation
        int inFM = 0;                       // # of KLFM for local optimum
        int outFM = 0;                      // Total # of KLFM
        int generation = 0;                 //
        String[] genOut = new String[4];    // For output
        int optCount = 0;                   // # of optimum found

        // Initialize population (with KLFM)
        Individual[] pop1 = new Individual[param.popSize];
        for (int i = 0; i < param.popSize; i++){
            pop1[i] = new Individual(param.nNode,graph);
            // Initialize with KLFM
            if(iniWithFM){
                inFM = iterKLFM(pop1[i]);
            }
            outFM = outFM + inFM;
            // Add counter if optimum found
            if(pop1[i].getFitness() >= param.optFit){
                System.out.print("*");
                optCount++;            // optCount: the probability of optimum found
            }
        }
        //outputStat(param.oFile,"EC-wrapped FM   popsize: "+param.popSize+"    MaxGen: "+param.maxGen);
        
        // Start evolving
        while((generation < param.maxGen)&&       // Maximum generation not reached
              (max!=param.optFit)&&               // Best fitness not found 
              (mean!=(double)max))                // Population are not converged 
        {
            Evolution evo = new Evolution();
            // Random selection
            Individual[] parents = evo.randSelection(pop1);
            // Generate off-spring via uniform crossover
            Individual offspring = new Individual(parents[0]);
            offspring.setGenes(evo.uniformCrossover(parents[0], parents[1]));
            // Re-initiate offspring by KLFM
            inFM = iterKLFM(offspring);
            outFM = outFM + inFM;
            // Add counter if optimum found
            if(offspring.getFitness() >= param.optFit){
                System.out.print("*");
                optCount++;            // optCount: the probability of optimum found
            }
            // Replace the worst case in the old population
            evo.replaceWorst(offspring,pop1);
            // Increment generation
            generation++;
            // Output
            genOut = (calStat(pop1));
            max = Integer.parseInt(genOut[1]);
            min = Integer.parseInt(genOut[2]);
            mean = Double.parseDouble(genOut[3]);
            // Output statistics for each generation - for testing 
            //genOut[0] = "\nGeneration: "+generation+"    Total FM: "+outFM+genOut[0];
            //outputStat(param.oFile,genOut[0]);
        }
        genOut[0] = "EC-wrapped FM   popsize: "+param.popSize+"    MaxGen: "+param.maxGen+
                    "\nGeneration: "+generation+"    Total FM: "+outFM+"    Total Opt: "+optCount+
                    genOut[0];
        outputStat(param.oFile,genOut[0]);
        System.out.print("\n");
        // Output statistics
        System.out.println(genOut[0]);
        System.out.println("Final population:");
        for (int i = 0; i < param.popSize; i++){
            System.out.print("  "+pop1[i].getFitness());
        }
        System.out.print(" \n");
    }
    
//---------------------------------------------------------------------
// Iterative KLFM
    /** Iterative KLFM till no improvement **/
    public int iterKLFM(final Individual ind){
        int countFM = 0;
        Individual tmpInd = new Individual(ind);
        KLFM fm1 = new KLFM(graph,ind);
        tmpInd = fm1.runKLFM();
        //System.out.println("Fitness increase: "+ind.getFitness()+" -> "+pop2[i].getFitness());    //For testing
        while(tmpInd.getFitness() > ind.getFitness()){
            ind.setGenes(tmpInd.getGenes());
            fm1.initKLFM(graph,ind);
            tmpInd = fm1.runKLFM();
            countFM++;
            System.out.print(".");    //For testing
            //System.out.println("Fitness increase: "+ind.getFitness()+" -> "+tmpInd.getFitness());    //For testing
        }
        //System.out.print("Fitness : "+ind.getFitness()+"    ");    //For testing
        return countFM;
    }

//---------------------------------------------------------------------
// Miscellaneous sub-functions    
    /** Sum up the values of an integer array **/
    public int sumArray(int[] a){
        int sum = 0;
        for(int i = 0; i < a.length; i++){
            sum += a[i];
        }
        return sum;
    }

    /** Show the values of an integer array **/
    public void showArray(int[] a){
        String output = "Array Values: \n";
        for(int i = 0; i < a.length; i++){
           output = output + " " + a[i];
           if((i%25)==24) output = output + "\n";
        }
        System.out.println(output);
    }
    
//---------------------------------------------------------------------
// Calculate and output statistics
    /** Compute statistics to a string[]:
        0 - Output string
        1 - max fitness
        2 - min fitness
        3 - mean fitness                **/
    public String[] calStat(final Individual[] pop){
        // Initialze statistics
        int max = -99999;
        int min = 99999;
        int sum = 0;
        double mean = 0.0;
        String[] output = new String[4];

        // Calculateion
        for (int i = 0; i < pop.length; i++){
            int fit = pop[i].getFitness();
            sum += fit;                         // Sum-up fitness
            if(fit > max){                      // max: maximum fitness
                max = fit;
            }
            if(fit < min){                      // min:  minimum fitness
                min = fit;
            }
        }
        mean = (double)sum/(double)pop.length;
        output[0] = "\n";
        output[1] = Integer.toString(max);
        output[2] = Integer.toString(min);
        output[3] = Double.toString(mean);

        // Make output
        output[0] += "Fitness  Mean: "+mean+"   Max: "+max+"    Min: "+min +"\n";
        return output;
    }

    /** Output statistics to a file **/
    public void outputStat(final String oFile, final String oString){
        PrintWriter result;
        
        // Open output file
        try {
            result = new PrintWriter(new FileWriter(oFile,true));
            //System.out.println("    Output file: "+oFile+" opened.");    // For testing
        } catch (IOException e) {
            System.out.println("Can't open file "+oFile+"!");
            System.out.println(e.toString());
            return;
        }
        
        // Write output
        //System.out.print("    outputing....");    // For testing
        result.println(oString);
        //System.out.print("    done!\n");    // For testing

        // Close output file
        result.close();
    }
}